package com.atguigu.gulimall.cart.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.cart.feign.ProductFeignService;
import com.atguigu.gulimall.cart.interceptor.CartInterceptor;
import com.atguigu.gulimall.cart.service.CartService;
import com.atguigu.gulimall.cart.vo.Cart;
import com.atguigu.gulimall.cart.vo.CartItem;
import com.atguigu.gulimall.cart.vo.SkuInfoVo;
import com.atguigu.gulimall.cart.vo.UserInfoTo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @Author 小坏
 * @Date 2021/1/28 11:38
 * @Version 1.0
 * @program: 父工程 gulimall 万物起源之地
 */
@Slf4j
@Service
public class CartServiceImpl implements CartService {
    private final String CART_PREFIX = "gulimall:cart:"; //存储redis的前缀
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    private ThreadPoolExecutor executor;

    /**
     * 用户登录了、肯定是用户登陆后的购物车添加商品
     * 没登陆、所有的操作才在临时购物车添加
     *
     * @param skuId
     * @param num
     * @return
     */

    @Override
    public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        BoundHashOperations<String, Object, Object> carOps = GetCarOps();

        String res = (String) carOps.get(skuId.toString());
        if (StringUtils.isEmpty(res)) {
            CartItem item = new CartItem();
            //购物车无此商品
            /**
             * 1、多个远程调用有的服务返回比加满、多个服务就浪费时间
             *      为了快可以使用多线程的方式、但是不能上来就开多线程的方式、
             *      我们要做的操作就是把所有线程全部放到自己指定的线程池里面、每个服务都配置一个自己的线程池
             *
             * 2、以后所有异步任务调度都由我们的这个线程池调度
             *  使用的是异步编排 CompletableFuture.runAsync(()--》异步的运行一个任务
             *
             *  3、executor：要交给自己的线程池
             *
             *  4、问题：
             *           CompletableFuture.allOf(getSkuInfoTask,getSkuSaleAttrValues).get();
             *                等待所有异步任务都完成、在执行下面、否则等待、
             *                如果不写这个等待、他直接返回数据、数据是空的
             *
             */

            //第一个异步任务、远程来查询sku信息、并把它封装起来
            CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
                //1、远程查询当前要添加的商品的信息、(想添加那个商品到购物车、先查出来这个商品)
                R skuInfo = productFeignService.getSkuInfo(skuId);
                //相当于购物车里面的购物项
                SkuInfoVo data = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                });

                //2、商品添加到购物车
                item.setCheck(true); //是否默认打勾
                item.setCount(num);//数量
                item.setImage(data.getSkuDefaultImg());//默认图片
                item.setTitle(data.getSkuTitle());//标题头
                item.setSkuId(skuId); //商品id
                item.setPrice(data.getPrice());//价格
            }, executor); //executor：要交给自己的线程池

            //第二个异步任务远程查询当前sku的组合信息
            CompletableFuture<Void> getSkuSaleAttrValues = CompletableFuture.runAsync(() -> {
                //3、远程调用查出当前sku的组合信息
                List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
                item.setSkuAttr(skuSaleAttrValues);
            }, executor);

            /**
             * 问题
             * CompletableFuture.allOf(getSkuInfoTask,getSkuSaleAttrValues).get();
             *      等待所有异步任务都完成、在执行下面、否则等待、
             *      如果不写这个等待、他直接返回数据、数据是空的
             */
            CompletableFuture.allOf(getSkuInfoTask, getSkuSaleAttrValues).get();
            String s = JSON.toJSONString(item);
            //添加到redis hash中
            carOps.put(skuId.toString(), s);
            return item;
        } else {
            //购物车有此商品、修改数量
            CartItem cartItem = JSON.parseObject(res, CartItem.class);
            cartItem.setCount(cartItem.getCount() + num);
            carOps.put(skuId.toString(), JSON.toJSONString(cartItem));
            return cartItem;
        }


    }

    @Override
    public CartItem getCartItem(Long skuId) {
        //获取到是临时还是登录的购物车
        BoundHashOperations<String, Object, Object> carOps = GetCarOps();
        //根据传递来的商品id查询
        String str = (String) carOps.get(skuId.toString());
        //转换为对象返回
        CartItem cartItem = JSON.parseObject(str, CartItem.class);
        return cartItem;
    }

    @Override
    public Cart getCart() throws ExecutionException, InterruptedException {
        /**
         * 1、登录和不登录状态
         *      不登录会有临时key存储购物车
         *      登录会用用户的id存储购物车
         */
        Cart cart = new Cart();
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        if (userInfoTo.getUserId() != null) {
            //1、登录
            String carKey = CART_PREFIX + userInfoTo.getUserId();
            //2、绑定redis的key、快速实现crud
//            BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(carKey);
            //3、如果临时的购物车还没有进行合并(先获取临时购物车)
            String tempCartkey = CART_PREFIX + userInfoTo.getUserKey();
            List<CartItem> cartItems = getCartItems(tempCartkey);
            if (cartItems != null) {
                //临时购物车有数据需要合并
                for (CartItem item : cartItems) {
                    //调用登录购物车传递 kuId、Count
                    addToCart(item.getSkuId(), item.getCount());
                }
                //4、清空临时购物车
                clearCart(tempCartkey);

            }

            //4、获取登录后的购物车的数据【包含合并过来的临时购物车数据，和登录的购物车的数据】
            List<CartItem> items = getCartItems(carKey);
            cart.setItems(items);  //存储到购物车、购物车的信息

        } else {
            //没有登录前缀加UserKey
            String carKey = CART_PREFIX + userInfoTo.getUserKey();
            //获取临时购物车的所有购物项
            List<CartItem> cartItems = getCartItems(carKey);
            //存储到购物车、购物车的信息
            cart.setItems(cartItems);
        }
        return cart;
    }

    /**
     * 清空购物车
     *
     * @param cartkey
     */
    @Override
    public void clearCart(String cartkey) {
        redisTemplate.delete(cartkey);
    }

    /**
     * 勾中购物项
     *
     * @param skuId
     * @param check
     */

    @Override
    public void checkItem(Long skuId, Integer check) {

        //获取购物车
        BoundHashOperations<String, Object, Object> carOps = GetCarOps();
        //获取购物项
        CartItem cartItem = getCartItem(skuId);
        //修改选中状态
        cartItem.setCheck(check == 1 ? true : false);
        String s = JSON.toJSONString(cartItem);
        //存储redis
        carOps.put(skuId.toString(), s);
    }

    /**
     * 购物车加减
     *
     * @param skuId
     * @param num
     */
    @Override
    public void changeItemCount(Long skuId, Integer num) {
        //1、获取购物项
        CartItem cartItem = getCartItem(skuId);
        //2、修改数量
        cartItem.setCount(num);
        //3、获取我们要操作的购物车
        BoundHashOperations<String, Object, Object> carOps = GetCarOps();
        //转换JSON添加到redis
        carOps.put(skuId.toString(), JSON.toJSONString(cartItem));

    }

    /**
     * 删除购物车
     *
     * @param skuId
     */
    @Override
    public void deleteItem(Long skuId) {
        //1、获取购物车
        BoundHashOperations<String, Object, Object> carOps = GetCarOps();
        //2、删除购物车
        carOps.delete(skuId.toString());
    }

    @Override
    public List<CartItem> getUserCarItems() {
        //登录成功访问过来是没用登录的、出现Feign远程调用丢失请求头问题
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        if (userInfoTo.getUserId() == null) {
            return null;
        } else {
            String tempCartkey = CART_PREFIX + userInfoTo.getUserId();
            List<CartItem> cartItems = getCartItems(tempCartkey);
            //获取所有被选中的购物项
            List<CartItem> collect = cartItems.stream()
                    .filter(item -> item.getCheck())
                    .map((item) -> {
                        //远程调用查出数据库的价格
                        R price = productFeignService.getPrice(item.getSkuId());
                        //TODO 更新为最新的价格
                        String data = (String) price.get("data");
                        item.setPrice(new BigDecimal(data));
                        return item;
                    }).collect(Collectors.toList());
            return collect;
        }

    }


    /**
     * 获取我们要操作的购物车
     *
     * @return
     */
    private BoundHashOperations<String, Object, Object> GetCarOps() {

        //只要是我们同一次请求、任何位置都能得到用户信息
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        String cartKey;
        if (userInfoTo.getUserId() != null) {
            //登录了gulimall:cart:1
            cartKey = CART_PREFIX + userInfoTo.getUserId();
        } else {
            //没登陆gulimall:cart:user-key
            cartKey = CART_PREFIX + userInfoTo.getUserKey();
        }

        //绑定一个hash、操作比较简单、直接可以crud
        BoundHashOperations<String, Object, Object> boundHashOps = redisTemplate.boundHashOps(cartKey);
        return boundHashOps;
    }

    /**
     * 获取购物车里面的所有购物项
     *
     * @param carKey
     * @return
     */
    private List<CartItem> getCartItems(String carKey) {
        //绑定redis的key、快速实现crud
        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(carKey);
        //获取redis 购物车的所有数据
        List<Object> values = ops.values();

        if (values != null && values.size() > 0) {
            //把List<Object>收集城List<对象>
            List<CartItem> collect = values.stream().map((obj) -> {
                String str = (String) obj;
                CartItem cartItem = JSON.parseObject(str, CartItem.class);
                return cartItem;
            }).collect(Collectors.toList());
            return collect;

        }
        return null;
    }
}
